基础

比较运算符

JavaScript中存在两种比较运算符,分别是== 和 === 两种:

第一种是’==’比较,它会自动转换数据类型再比较,很多时候,会得到非常诡异的结果;

第二种是’===’比较,它不会自动转换数据类型,如果数据类型不一致,返回false,如果一致,再比较。

由于JavaScript这个设计缺陷,不要使用 == 比较,始终坚持使用 === 比较。

NaN

另一个例外是NaN这个特殊的Number与所有其他值都不相等,包括它自己:

1
NaN === NaN; // false

唯一能判断NaN的方法是通过isNaN()函数:

1
isNaN(NaN); // true

null、undefined

  • null

null表示一个空值,是一种广义上的“空”,是类型无关的;

和null比较,0表示的是一个数值;空字符’ ‘表示的长度为0的字符串;

  • undefined

表示未定义,也有“空”的意思,大多数情况用来表示一个函数的参数没有被传递过来;

数组

JavaScript中的数组起着类似于其他语言中集合的作用,其提供了许多操作结合的函数;

JavaScript中的数组是类型无关的,其中可以包含任意的数据类型。

1
[1, 2, 3.14, 'Hello', null, true];

另一种定义数组的方式:

1
new Array(1, 2, 3); // 创建了数组[1, 2, 3]

array.length可以用来获取数组的长度,同时,还可以通过给array.length赋值来改变数组的长度:

1
2
3
4
5
6
7
8
9
10
11
var arr = [1, 2, 3];
arr.length; // 3
arr.length = 6;
arr; // arr变为[1, 2, 3, undefined, undefined, undefined]
arr.length = 2;
arr; // arr变为[1, 2]

// 访问数组越界也会导致数组的大小改变
var arr = [1, 2, 3];
arr[5] = 'x';
arr; // arr变为[1, 2, 3, undefined, undefined, 'x']

数组操作

  • push、pop实现数组尾部的增删操作,push支持一次性操作多个元素;
  • unshift、shift实现数组头部元素的增删操作,unshift支持多个元素的操作;
  • slice对应了字符串中的subString操作,直接调用slice()函数可以方便的复制整个数组;
  • splice可以直接对数据某个位置上的元素进行增删;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    var arr = ['Microsoft', 'Apple', 'Yahoo', 'AOL', 'Excite', 'Oracle'];
    // 从索引2开始删除3个元素,然后再添加两个元素:
    arr.splice(2, 3, 'Google', 'Facebook'); // 返回删除的元素 ['Yahoo', 'AOL', 'Excite']
    arr; // ['Microsoft', 'Apple', 'Google', 'Facebook', 'Oracle']
    // 只删除,不添加:
    arr.splice(2, 2); // ['Google', 'Facebook']
    arr; // ['Microsoft', 'Apple', 'Oracle']
    // 只添加,不删除:
    arr.splice(2, 0, 'Google', 'Facebook'); // 返回[],因为没有删除任何元素
    arr; // ['Microsoft', 'Apple', 'Google', 'Facebook', 'Oracle']
  • join()能够用制定的连接符将数组中的所有元素连接起来;

Map、Set集合

ES6新增了Map和Set集合结果;

  • Map

Map如果直接初始化,需要使用一个二维数组进行初始化:

1
2
var m = new Map([['Michael', 95], ['Bob', 75], ['Tracy', 85]]);
m.get('Michael'); // 95

直接设置:

1
2
3
4
5
6
7
var m = new Map(); // 空Map
m.set('Adam', 67); // 添加新的key-value
m.set('Bob', 59);
m.has('Adam'); // 是否存在key 'Adam': true
m.get('Adam'); // 67
m.delete('Adam'); // 删除key 'Adam'
m.get('Adam'); // undefined

  • Set可以理解为没有重复元素的数组
1
2
3
4
5
6
7
8
s.add(4);
s; // Set {1, 2, 3, 4}
s.add(4);
s; // 仍然是 Set {1, 2, 3, 4}

s; // Set {1, 2, 3}
s.delete(3);
s; // Set {1, 2}
  • for..of

for..of是ES6引进来的遍历Array、Map和Set类型集合的循环,不同于for..in,for..of只遍历原始集合中数据,而对于动态添加的数据不进行遍历:

1
2
3
4
5
6
7
// for .. in 原先是用来遍历对象的所有属性名的,而Array可以认为是一个只有属性名,没有属性值的对象
// 也可以向其中动态添加属性键值对;
var a = ['A', 'B', 'C'];
a.name = 'Hello';
for (var x in a) {
console.log(x); // '0', '1', '2', 'name'
}
1
2
3
4
5
var a = ['A', 'B', 'C'];
a.name = 'Hello';
for (var x of a) {
console.log(x); // 'A', 'B', 'C'
}

对象

JavaScript的对象是由一组键-值组成的无序集合(类似于Python中的字典):

1
2
3
4
5
6
7
8
var person = {
name: 'Bob',
age: 20,
tags: ['js', 'web', 'mobile'],
city: 'Beijing',
hasCar: true,
zipcode: null
};

其中的键值对的键又称为对象的属性,都是字符串类型,值则可以是任意类型;

  • 对象直接直接添加或删除属性

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    var xiaoming = {
    name: '小明'
    };
    xiaoming.age; // undefined
    xiaoming.age = 18; // 新增一个age属性
    xiaoming.age; // 18
    delete xiaoming.age; // 删除age属性
    xiaoming.age; // undefined
    delete xiaoming['name']; // 删除name属性
    xiaoming.name; // undefined
    delete xiaoming.school; // 删除一个不存在的school属性也不会报错
  • 判断是否拥有某属性

in可以用来判断对象是否拥有某属性,包含了继承的属性:

1
2
3
4
5
6
7
8
9
10
var xiaoming = {
name: '小明',
birth: 1990,
school: 'No.1 Middle School',
height: 1.70,
weight: 65,
score: null
};
'name' in xiaoming; // true
'grade' in xiaoming; // false

hasOwnProperty用来判断某对象是否自带某属性,不包含继承的:

1
2
3
4
5
var xiaoming = {
name: '小明'
};
xiaoming.hasOwnProperty('name'); // true
xiaoming.hasOwnProperty('toString'); // false

  • for..in遍历对象或数组属性
    1
    2
    3
    4
    5
    6
    7
    8
    var o = {
    name: 'Jack',
    age: 20,
    city: 'Beijing'
    };
    for (var key in o) {
    console.log(key); // 'name', 'age', 'city'
    }
1
2
3
4
5
var a = ['A', 'B', 'C'];
for (var i in a) {
console.log(i); // '0', '1', '2'
console.log(a[i]); // 'A', 'B', 'C'
}

方法

函数

  • arguments是函数内部的关键字,可以用来获取传给函数的所有参数,

    1
    2
    3
    4
    5
    6
    7
    function foo(x) {
    console.log('x = ' + x); // 10
    for (var i=0; i<arguments.length; i++) {
    console.log('arg ' + i + ' = ' + arguments[i]); // 10, 20, 30
    }
    }
    foo(10, 20, 30);
  • rest参数
    传递给函数的参数并不需要完全参照函数定义个参数个数来确定,我们可以传递任意个数的参数给函数;

对于额外传递给函数的参数,我们可以通过rest参数来获取(ES6引入);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function foo(a, b, ...rest) {
console.log('a = ' + a);
console.log('b = ' + b);
console.log(rest);
}

foo(1, 2, 3, 4, 5);
// 结果:
// a = 1
// b = 2
// Array [ 3, 4, 5 ]

foo(1);
// 结果:
// a = 1
// b = undefined
// Array []

命名空间

  • 在JavaScript中,凡是没有定义在函数中的变量或者函数,都是全局变量;
  • 全局变量缔属于window对象,所有的全局变量或者函数都可以使用window对象来调起;

    1
    2
    3
    4
    5
    6
    7
    8
    'use strict';

    function foo() {
    alert('foo');
    }

    foo(); // 直接调用foo()
    window.foo(); // 通过window.foo()调用
  • 因为不同的JavaScript文件可能定义了相同名字的全局变量或者函数,这将导致冲突,为了解决冲突,我们可以为每一个JavaScript文件定义一个命名空间;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // 唯一的全局变量MYAPP:
    var MYAPP = {};

    // 其他变量:
    MYAPP.name = 'myapp';
    MYAPP.version = 1.0;

    // 其他函数:
    MYAPP.foo = function () {
    return 'foo';
    };

局部作用域

在JavaScript中,局部作用域一般是只有函数内部的局部作用,而不像Java等,存在块级的作用域:

1
2
3
4
5
6
7
8
'use strict';

function foo() {
for (var i=0; i<100; i++) {
//
}
i += 100; // 仍然可以引用变量i
}

ES6引入新的let关键字,用来替代var,可以用来声明一个块级的作用域(代码块!)

1
2
3
4
5
6
7
8
9
10
'use strict';

function foo() {
var sum = 0;
for (let i=0; i<100; i++) {
sum += i;
}
// SyntaxError:
i += 1;
}

strict模式与全局变量

在JavaScript中,变量不同过var修饰,则为全局变量;

如果多个JavaScript文件中存在同名的全局变量,那么将会带来不可预知的错误;

解决方案是使用strict模式:只需要在JavaScript代码的第一行添加:

1
'use strict';

那么支持strict模式的浏览器将强制用var声明变量,否则将报ReferenceError错误;

字符串操作

JavaScript中字符串是一个常量,对其任何操作都不会改变其原内容,而是返回一个新的字符串结果。

字符串可以认为是一串字符数组;

  • 多行字符串

新的ES6标准允许我们直接使用回车作为换行符:使用反引号...进行括起;

1
2
3
`这是一个
多行
字符串`;
  • 字符串连接

在JavaScript中,多个字符串要连接成一个字符串,可以使用“+”进行连接;

在ES6中,支持我们使用字符串模板的形式进行连接(必须使用反引号来括起内容):

1
2
3
4
var name = '小明';
var age = 20;
var message = `你好, ${name}, 你今年${age}岁了!`;
alert(message);

ES6新特性

  • 常量:可以使用const来声明一个常量;

    1
    2
    3
    4
    5
    'use strict';

    const PI = 3.14;
    PI = 3; // 某些浏览器不报错,但是无效果!
    PI; // 3.14
  • 解构赋值:将数组的元素同时赋给多个变量;

    1
    2
    // 如果浏览器支持解构赋值就不会报错:
    var [x, y, z] = ['hello', 'JavaScript', 'ES6'];

解构赋值可以进行灵活的运用,比如对二维数组进行解构:

1
2
3
4
let [x, [y, z]] = ['hello', ['JavaScript', 'ES6']];
x; // 'hello'
y; // 'JavaScript'
z; // 'ES6'

忽略部分元素,只解构出一部分元素:

1
2
let [, , z] = ['hello', 'JavaScript', 'ES6']; // 忽略前两个元素,只对z赋值第三个元素
z; // 'ES6'
  • 解构对象:解构对象需要使用对应的属性名,并且用大括号括起来;
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    'use strict';

    var person = {
    name: '小明',
    age: 20,
    gender: 'male',
    passport: 'G-12345678',
    school: 'No.4 middle school'
    };
    var {name, age, passport} = person;

同样,对于嵌套的对象,也可以解构出其嵌套值;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var person = {
name: '小明',
age: 20,
gender: 'male',
passport: 'G-12345678',
school: 'No.4 middle school',
address: {
city: 'Beijing',
street: 'No.1 Road',
zipcode: '100001'
}
};
var {name, address: {city, zip}} = person;
name; // '小明'
city; // 'Beijing'
zip; // undefined, 因为属性名是zipcode而不是zip
// 注意: address不是变量,而是为了让city和zip获得嵌套的address对象的属性:
address; // Uncaught ReferenceError: address is not defined

解构对象时,并不一定要于对象的的属性名一致,可以另外指定其他变量来接收解构的属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var person = {
name: '小明',
age: 20,
gender: 'male',
passport: 'G-12345678',
school: 'No.4 middle school'
};

// 把passport属性赋值给变量id:
let {name, passport:id} = person;
name; // '小明'
id; // 'G-12345678'
// 注意: passport不是变量,而是为了让变量id获得passport属性:
passport; // Uncaught ReferenceError: passport is not defined

可以给要获取解构的属性的变量设置默认值:

1
2
3
4
5
6
7
8
9
10
11
var person = {
name: '小明',
age: 20,
gender: 'male',
passport: 'G-12345678'
};

// 如果person对象没有single属性,默认赋值为true:
var {name, single=true} = person;
name; // '小明'
single; // true

  • 解构对象时注意点

对于已经声明的变量,用它们来接收解构属性时,需要用括号括起,表示是一个运行语句,而不是代码块:

1
2
3
4
5
// 声明变量:
var x, y;
// 解构赋值:
{x, y} = { name: '小明', x: 100, y: 200};
// 语法错误: Uncaught SyntaxError: Unexpected token =

因为JavaScript引擎把{开头的语句当作了块处理,于是=不再合法。

1
2
正确写法:
({x, y} = { name: '小明', x: 100, y: 200});

解构赋值使用场景

  • 方便交换变量内容
1
2
var x=1, y=2;
[x, y] = [y, x]
  • 解构对象内容

    1
    var {hostname:domain, pathname:path} = location;
  • 解构作为函数参数的参数,实现具名参数和默认参数

    1
    2
    3
    4
    5
    6
    7
    8
    // 解构时可以设置默认值
    function buildDate({year, month, day, hour=0, minute=0, second=0}) {
    return new Date(year + '-' + month + '-' + day + ' ' + hour + ':' + minute + ':' + second);
    }

    // 传递的对象的属性顺序并不要求是固定的
    buildDate({ year: 2017, month: 1, day: 1 });
    // Sun Jan 01 2017 00:00:00 GMT+0800 (CST)